/************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * Copyright 2008, 2010 Oracle and/or its affiliates. All rights reserved.
 * 
 * Use is subject to license terms.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy
 * of the License at http://www.apache.org/licenses/LICENSE-2.0. You can also
 * obtain a copy of the License at http://odftoolkit.org/docs/license.txt
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * 
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 ************************************************************************/
package org.odftoolkit.odfdom.doc;

import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;

import org.odftoolkit.odfdom.pkg.OdfElement;
import org.odftoolkit.odfdom.pkg.OdfFileDom;
import org.odftoolkit.odfdom.pkg.OdfName;
import org.odftoolkit.odfdom.pkg.OdfNamespace;
import org.odftoolkit.odfdom.doc.presentation.OdfSlide;
import org.odftoolkit.odfdom.dom.OdfContentDom;
import org.odftoolkit.odfdom.dom.OdfDocumentNamespace;
import org.odftoolkit.odfdom.dom.OdfStylesDom;
import org.odftoolkit.odfdom.dom.attribute.presentation.PresentationClassAttribute;
import org.odftoolkit.odfdom.dom.element.OdfStyleBase;
import org.odftoolkit.odfdom.dom.element.draw.DrawFrameElement;
import org.odftoolkit.odfdom.dom.element.draw.DrawPageElement;
import org.odftoolkit.odfdom.dom.element.draw.DrawPageThumbnailElement;
import org.odftoolkit.odfdom.dom.element.office.OfficePresentationElement;
import org.odftoolkit.odfdom.dom.element.presentation.PresentationNotesElement;
import org.odftoolkit.odfdom.dom.element.style.StyleGraphicPropertiesElement;
import org.odftoolkit.odfdom.dom.element.style.StylePresentationPageLayoutElement;
import org.odftoolkit.odfdom.incubator.doc.office.OdfOfficeAutomaticStyles;
import org.odftoolkit.odfdom.incubator.doc.office.OdfOfficeStyles;
import org.odftoolkit.odfdom.pkg.MediaType;
import org.odftoolkit.odfdom.pkg.OdfPackage;
import org.odftoolkit.odfdom.pkg.OdfPackageDocument;
import org.odftoolkit.odfdom.pkg.manifest.OdfFileEntry;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * This class represents an empty ODF presentation.
 * 
 * @deprecated As of release 0.8.8, replaced by {@link org.odftoolkit.simple.PresentationDocument} in Simple API.
 */
public class OdfPresentationDocument extends OdfDocument {

	private static final String EMPTY_PRESENTATION_DOCUMENT_PATH = "/OdfPresentationDocument.odp";
	static final Resource EMPTY_PRESENTATION_DOCUMENT_RESOURCE = new Resource(EMPTY_PRESENTATION_DOCUMENT_PATH);

	/**
	 * This enum contains all possible media types of OdfPresentationDocument
	 * documents.
	 */
	public enum OdfMediaType implements MediaType {

		PRESENTATION(OdfDocument.OdfMediaType.PRESENTATION),
		PRESENTATION_TEMPLATE(OdfDocument.OdfMediaType.PRESENTATION_TEMPLATE);
		private final OdfDocument.OdfMediaType mMediaType;

		OdfMediaType(OdfDocument.OdfMediaType mediaType) {
			this.mMediaType = mediaType;
		}

		/**
		 * @return the ODF mediatype of this document
		 */
		public OdfDocument.OdfMediaType getOdfMediaType() {
			return mMediaType;
		}

		/**
		 * @return the mediatype of this document
		 */
		public String getMediaTypeString() {
			return mMediaType.getMediaTypeString();
		}

		/**
		 * @return the ODF filesuffix of this document
		 */
		public String getSuffix() {
			return mMediaType.getSuffix();
		}

		/**
		 *
		 * @param mediaType string defining an ODF document
		 * @return the according OdfMediatype encapuslating the given string and the suffix
		 */
		public static OdfDocument.OdfMediaType getOdfMediaType(String mediaType) {
			return OdfDocument.OdfMediaType.getOdfMediaType(mediaType);
		}
	}

	/**
	 * Creates an empty presentation document.
	 * @return ODF presentation document based on a default template
	 * @throws java.lang.Exception - if the document could not be created
	 */
	public static OdfPresentationDocument newPresentationDocument() throws Exception {
		return (OdfPresentationDocument) OdfDocument.loadTemplate(EMPTY_PRESENTATION_DOCUMENT_RESOURCE, OdfDocument.OdfMediaType.PRESENTATION);
	}

	/**
	 * Creates an empty presentation template.
	 * @return ODF presentation template based on a default
	 * @throws Exception - if the template could not be created
	 */
	public static OdfPresentationDocument newPresentationTemplateDocument() throws Exception {
		OdfPresentationDocument doc = (OdfPresentationDocument) OdfDocument.loadTemplate(EMPTY_PRESENTATION_DOCUMENT_RESOURCE, OdfDocument.OdfMediaType.PRESENTATION_TEMPLATE);
		doc.changeMode(OdfMediaType.PRESENTATION_TEMPLATE);
		return doc;
	}

	/** To avoid data duplication a new document is only created, if not already opened.
	 * A document is cached by this constructor using the internalpath as key. */
	protected OdfPresentationDocument(OdfPackage pkg, String internalPath, OdfPresentationDocument.OdfMediaType odfMediaType) throws SAXException {
		super(pkg, internalPath, odfMediaType.mMediaType);
	}

	/**
	 * Creates an OdfPresentationDocument from the OpenDocument provided by a resource Stream.
	 *
	 * <p>Since an InputStream does not provide the arbitrary (non sequentiell)
	 * read access needed by OdfPresentationDocument, the InputStream is cached. This usually
	 * takes more time compared to the other createInternalDocument methods.
	 * An advantage of caching is that there are no problems overwriting
	 * an input file.</p>
	 * 
	 * <p>If the resource stream is not a ODF presentation document, ClassCastException might be thrown.</p>
	 *
	 * @param inputStream - the InputStream of the ODF presentation document.
	 * @return the presentation document created from the given InputStream
	 * @throws java.lang.Exception - if the document could not be created.
	 */
	public static OdfPresentationDocument loadDocument(InputStream inputStream) throws Exception {
		return (OdfPresentationDocument) OdfDocument.loadDocument(inputStream);
	}

	/**
	 * Loads an OdfPresentationDocument from the provided path.
	 *
	 * <p>OdfPresentationDocument relies on the file being available for read access over
	 * the whole lifecycle of OdfPresentationDocument.</p>
	 *
	 * <p>If the resource stream is not a ODF presentation document, ClassCastException might be thrown.</p>
	 * 
	 * @param documentPath - the path from where the document can be loaded
	 * @return the presentation document from the given path
	 *		  or NULL if the media type is not supported by ODFDOM.
	 * @throws java.lang.Exception - if the document could not be created.
	 */
	public static OdfPresentationDocument loadDocument(String documentPath) throws Exception {
		return (OdfPresentationDocument) OdfDocument.loadDocument(documentPath);
	}

	/**
	 * Creates an OdfPresentationDocument from the OpenDocument provided by a File.
	 *
	 * <p>OdfPresentationDocument relies on the file being available for read access over
	 * the whole lifecycle of OdfPresentationDocument.</p>
	 *
	 * <p>If the resource stream is not a ODF presentation document, ClassCastException might be thrown.</p>
	 * 
	 * @param file - a file representing the ODF presentation document.
	 * @return the presentation document created from the given File
	 * @throws java.lang.Exception - if the document could not be created.
	 */
	public static OdfPresentationDocument loadDocument(File file) throws Exception {
		return (OdfPresentationDocument) OdfDocument.loadDocument(file);
	}

	/**
	 * Get the content root of a presentation document.
	 *
	 * @return content root, representing the office:presentation tag
	 * @throws Exception if the file DOM could not be created.
	 */
	@Override
	public OfficePresentationElement getContentRoot() throws Exception {
		return super.getContentRoot(OfficePresentationElement.class);
	}

	/**
	 * Switches this instance to the given type. This method can be used to e.g.
	 * convert a document instance to a template and vice versa.
	 * Changes take affect in the package when saving the document.
	 *
	 * @param type the compatible ODF mediatype.
	 */
	public void changeMode(OdfMediaType type) {
		setOdfMediaType(type.mMediaType);
	}
	private boolean hasCheckSlideName = false;
	//if the copy foreign slide for several times, 
	//the same style might be copied for several times with the different name
	//so use styleRenameMap to keep track the renamed style so we can reuse the style, 
	//rather than new several styles which only have the different style names.
	//while if the style elements really have the same style name but with different content
	//such as that these style elements are from different document
	//so the value for each key should be a list
	private HashMap<String, List<String>> styleRenameMap = new HashMap<String, List<String>>();
	//the map is used to record if the renamed style name is appended to the current dom
	private HashMap<String, Boolean> styleAppendMap = new HashMap<String, Boolean>();
	//the object rename map for image.
	//can not easily recognize if the embedded document are the same.
//	private HashMap<String, String> objectRenameMap = new HashMap<String, String>();

	/**
	 * Return the slide at a specified position in this presentation.
	 * Return null if the index is out of range.
	 *
	 * @param index	the index of the slide to be returned
	 * @return	a draw slide at the specified position
	 */
	public OdfSlide getSlideByIndex(int index) {
		checkAllSlideName();
		OfficePresentationElement contentRoot = null;
		try {
			contentRoot = getContentRoot();
		} catch (Exception e) {
			Logger.getLogger(OdfPresentationDocument.class.getName()).log(Level.SEVERE, null, e);
			return null;
		}
		NodeList slideNodes = contentRoot.getElementsByTagNameNS(OdfDocumentNamespace.DRAW.getUri(), "page");
		if ((index >= slideNodes.getLength()) || (index < 0)) {
			return null;
		}
		DrawPageElement slideElement = (DrawPageElement) slideNodes.item(index);
		return OdfSlide.getInstance(slideElement);
	}

	/**
	 * Get the number of the slides in this presentation.
	 *
	 * @return	the number of slides
	 */
	public int getSlideCount() {
		checkAllSlideName();
		OfficePresentationElement contentRoot = null;
		try {
			contentRoot = getContentRoot();
		} catch (Exception e) {
			Logger.getLogger(OdfPresentationDocument.class.getName()).log(Level.SEVERE, null, e);
			return 0;
		}
		NodeList slideNodes = contentRoot.getElementsByTagNameNS(OdfDocumentNamespace.DRAW.getUri(), "page");
		return slideNodes.getLength();
	}

	/**
	 * Return the slide which have a specified slide name in this presentation.
	 * <p>
	 * According to the odf specification
	 * "The draw:name attribute specifies a name by which this element can be referenced. 
	 * It is optional but if present, must be unique within the document instance. 
	 * If not present, an application may generate a unique name."
	 * <p>
	 * If the name is null, then return null because all the slide must has its own unique name.
	 * 
	 * @param name	the specified slide name
	 * @return	the slide whose name equals to the specified name
	 */
	public OdfSlide getSlideByName(String name) {
		checkAllSlideName();
		if (name == null) {
			return null;
		}
		OfficePresentationElement contentRoot = null;
		try {
			contentRoot = getContentRoot();
		} catch (Exception e) {
			Logger.getLogger(OdfPresentationDocument.class.getName()).log(Level.SEVERE, null, e);
			return null;
		}
		NodeList slideNodes = contentRoot.getElementsByTagNameNS(OdfDocumentNamespace.DRAW.getUri(), "page");
		for (int i = 0; i < slideNodes.getLength(); i++) {
			DrawPageElement slideElement = (DrawPageElement) slideNodes.item(i);
			OdfSlide slide = OdfSlide.getInstance(slideElement);
			String slideName = slide.getSlideName();
			if (slideName.equals(name)) {
				return slide;
			}
		}
		return null;
	}

	//when access slide related method, this function should be called
	private void checkAllSlideName() {
		//check if this function is called or not
		if (hasCheckSlideName) {
			return;
		}
		List<String> slideNameList = new ArrayList<String>();
		OfficePresentationElement contentRoot = null;
		try {
			contentRoot = getContentRoot();
		} catch (Exception e) {
			Logger.getLogger(OdfPresentationDocument.class.getName()).log(Level.SEVERE, null, e);
			return;
		}
		NodeList slideNodes = contentRoot.getElementsByTagNameNS(OdfDocumentNamespace.DRAW.getUri(), "page");
		for (int i = 0; i < slideNodes.getLength(); i++) {
			DrawPageElement slideElement = (DrawPageElement) slideNodes.item(i);
			String slideName = slideElement.getDrawNameAttribute();
			if ((slideName == null) || slideNameList.contains(slideName)) {
				slideName = "page" + (i + 1) + "-" + makeUniqueName();
				slideElement.setDrawNameAttribute(slideName);
			}
			slideNameList.add(slideName);
		}
		hasCheckSlideName = true;
	}

	/**
	 * Return a list iterator containing all slides in this presentation.
	 *
	 * @return	a list iterator containing all slides in this presentation
	 */
	public Iterator<OdfSlide> getSlides() {
		checkAllSlideName();
		OfficePresentationElement contentRoot = null;
		try {
			contentRoot = getContentRoot();
		} catch (Exception e) {
			Logger.getLogger(OdfPresentationDocument.class.getName()).log(Level.SEVERE, null, e);
			return null;
		}
		ArrayList<OdfSlide> slideList = new ArrayList<OdfSlide>();
		NodeList slideNodes = contentRoot.getElementsByTagNameNS(OdfDocumentNamespace.DRAW.getUri(), "page");
		for (int i = 0; i < slideNodes.getLength(); i++) {
			DrawPageElement slideElement = (DrawPageElement) slideNodes.item(i);
			slideList.add(OdfSlide.getInstance(slideElement));
		}
		return slideList.iterator();
	}

	/**
	 * Delete the slide at a specified position in this presentation.
	 *
	 * @param index	the index of the slide that need to be delete
	 * <p>
	 * Throw IndexOutOfBoundsException if the slide index is out of the presentation document slide count.
	 * @return false if the operation was not successful
	 */
	public boolean deleteSlideByIndex(int index) {
		boolean success = true;
		checkAllSlideName();
		OfficePresentationElement contentRoot = null;
		try {
			contentRoot = getContentRoot();
		} catch (Exception e) {
			Logger.getLogger(OdfPresentationDocument.class.getName()).log(Level.SEVERE, null, e);
			success = false;
			return success;
		}
		NodeList slideNodes = contentRoot.getElementsByTagNameNS(OdfDocumentNamespace.DRAW.getUri(), "page");
		if ((index >= slideNodes.getLength()) || (index < 0)) {
			throw new IndexOutOfBoundsException("the specified Index is out of slide count when call deleteSlideByIndex method.");
		}
		DrawPageElement slideElement = (DrawPageElement) slideNodes.item(index);
		//remove all the content of the current page
		//1. the reference of the path that contained in this slide is 1, then remove it
		success &= deleteLinkRef(slideElement);
		//2.the reference of the style is 1, then remove it
		//in order to save time, do not delete the style here
		success &= deleteStyleRef(slideElement);
		//remove the current page element
		contentRoot.removeChild(slideElement);
		adjustNotePageNumber(index);
		return success;
	}

	private boolean deleteStyleRef(DrawPageElement slideEle) {
		boolean success = true;
		try {			
			//method 1:
			//1.1. iterate child element of the content element
			//1.2. if the child element is an OdfStylableElement, get the style-name ref count
			////////////////
			//method 2:
			//2.1. get the list of the style definition
			ArrayList<OdfElement> removeStyles = new ArrayList<OdfElement>();
			OdfOfficeAutomaticStyles autoStyles = getContentDom().getAutomaticStyles();

			NodeList stylesList = autoStyles.getChildNodes();
			OdfContentDom contentDom = getContentDom();
			XPath xpath = contentDom.getXPath();

			//2.2. get the reference of each style which occurred in the current page
			for (int i = 0; i < stylesList.getLength(); i++) {
				Node item = stylesList.item(i);
				if (item instanceof OdfElement) {
					OdfElement node = (OdfElement) item;
					String styleName = node.getAttributeNS(OdfDocumentNamespace.STYLE.getUri(), "name");
					if (styleName != null) {
						//search the styleName contained at the current page element
						NodeList styleNodes = (NodeList) xpath.evaluate("//*[@*='" + styleName + "']", contentDom, XPathConstants.NODESET);
						int styleCnt = styleNodes.getLength();
						if (styleCnt > 1) {
							//the first styleName is occurred in the style definition
							//so check if the second styleName and last styleName is occurred in the current page element
							//if yes, then remove it
							OdfElement elementFirst = (OdfElement) styleNodes.item(1);
							OdfElement elementLast = (OdfElement) styleNodes.item(styleCnt - 1);
							boolean isSamePage = false;
							if (elementFirst instanceof DrawPageElement) {
								DrawPageElement tempPage = (DrawPageElement) elementFirst;
								if (tempPage.equals(slideEle)) {
									isSamePage = true;
								}
							}
							int relationFirst = slideEle.compareDocumentPosition(elementFirst);
							int relationLast = slideEle.compareDocumentPosition(elementLast);
							//if slide element contains the child element which has the styleName reference
							if (((relationFirst & Node.DOCUMENT_POSITION_CONTAINED_BY) > 0
									&& (relationLast & Node.DOCUMENT_POSITION_CONTAINED_BY) > 0)
									|| (isSamePage && (styleCnt == 1))) {
								if (node instanceof OdfStyleBase) {
									removeStyles.add(node);
								}
							}
						} else {
							continue;
						}
					}
				}
			}
			for (int i = 0; i < removeStyles.size(); i++) {
				autoStyles.removeChild(removeStyles.get(i));
			}
		} catch (Exception e) {
			Logger.getLogger(OdfPresentationDocument.class.getName()).log(Level.SEVERE, null, e);
			success = false;
		}
		return success;
	}

	//delete all the xlink:href object which is contained in slideElement and does not referred by other slides
	private boolean deleteLinkRef(DrawPageElement slideEle) {
		boolean success = true;
		try {
			OdfContentDom contentDom = getContentDom();
			XPath xpath = contentDom.getXPath();
			NodeList linkNodes = (NodeList) xpath.evaluate("//*[@xlink:href]", contentDom, XPathConstants.NODESET);
			for (int i = 0; i < linkNodes.getLength(); i++) {
				OdfElement object = (OdfElement) linkNodes.item(i);
				String refObjPath = object.getAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "href");
				int relation = slideEle.compareDocumentPosition(object);
				//if slide element contains the returned element which has the xlink:href reference
				if ((relation & Node.DOCUMENT_POSITION_CONTAINED_BY) > 0 && refObjPath != null && refObjPath.length() > 0) {
					//the path of the object is start with "./"
					NodeList pathNodes = (NodeList) xpath.evaluate("//*[@xlink:href='" + refObjPath + "']", getContentDom(), XPathConstants.NODESET);
					int refCount = pathNodes.getLength();
					if (refCount == 1) {
						//delete "./"
						if (refObjPath.startsWith("./")) {
							refObjPath = refObjPath.substring(2);
						}
						//check if the current document contains the same path
						OdfFileEntry fileEntry = getPackage().getFileEntry(refObjPath);
						if (fileEntry != null) {
							//it is a stream, such as image, binary file
							getPackage().remove(refObjPath);
						} else {
							//note: if refObjPath is a directory, it must end with '/'
							fileEntry = getPackage().getFileEntry(refObjPath + "/");
							removeDocument(refObjPath);
						}
					}
				}
			}
		} catch (XPathExpressionException e) {
			Logger.getLogger(OdfPresentationDocument.class.getName()).log(Level.SEVERE, null, e);
			success = false;
		} catch (Exception e) {
			Logger.getLogger(OdfPresentationDocument.class.getName()).log(Level.SEVERE, null, e);
			success = false;
		}
		return success;
	}

	/**
	 * Delete all the slides with a specified name in this presentation.
	 *
	 * @param name	the name of the slide that need to be delete
	 * @return false if the operation was not successful
	 */
	public boolean deleteSlideByName(String name) {
		boolean success = true;
		checkAllSlideName();
		OfficePresentationElement contentRoot = null;
		try {
			contentRoot = getContentRoot();
		} catch (Exception e) {
			Logger.getLogger(OdfPresentationDocument.class.getName()).log(Level.SEVERE, null, e);
			success = false;
			return success;
		}
		OdfSlide slide = getSlideByName(name);
		DrawPageElement slideElement = slide.getOdfElement();
		//remove all the content of the current page
		//1. the reference of the path that contained in this slide is 1, then remove its
		success &= deleteLinkRef(slideElement);
		//2.the reference of the style is 1, then remove it
		//in order to save time, do not delete style here
		success &= deleteStyleRef(slideElement);
		//remove the current page element
		contentRoot.removeChild(slideElement);
		adjustNotePageNumber(0);
		return success;
	}

	/**
	 * Make a copy of the slide at a specified position to another position in this presentation.
	 * The original slide which at the dest index and after the dest index will move after.
	 * <p>
	 * @param source	the source position of the slide need to be copied 
	 * @param dest		the destination position of the slide need to be copied
	 * @param newName	the new name of the copied slide
	 * @return the new slide at the destination position with the specified name, and it has the same content 
	 * with the slide at the source position.
	 * <p>
	 * Throw IndexOutOfBoundsException if the slide index is out of the presentation document slide count.
	 * If copy the slide at the end of document, destIndex should set the same value with the slide count.
	 */
	public OdfSlide copySlide(int source, int dest, String newName) {
		checkAllSlideName();
		OfficePresentationElement contentRoot = null;
		try {
			contentRoot = getContentRoot();
		} catch (Exception e) {
			Logger.getLogger(OdfPresentationDocument.class.getName()).log(Level.SEVERE, null, e);
			return null;
		}
		NodeList slideList = contentRoot.getElementsByTagNameNS(OdfDocumentNamespace.DRAW.getUri(), "page");
		int slideCount = slideList.getLength();
		if ((source < 0) || (source >= slideCount)
				|| (dest < 0) || (dest > slideCount)) {
			throw new IndexOutOfBoundsException("the specified Index is out of slide count when call copySlide method.");
		}
		DrawPageElement sourceSlideElement = (DrawPageElement) slideList.item(source);
		DrawPageElement cloneSlideElement = (DrawPageElement) sourceSlideElement.cloneNode(true);
		cloneSlideElement.setDrawNameAttribute(newName);
		if (dest == slideCount) {
			contentRoot.appendChild(cloneSlideElement);
		} else {
			DrawPageElement refSlide = (DrawPageElement) slideList.item(dest);
			contentRoot.insertBefore(cloneSlideElement, refSlide);
		}
		adjustNotePageNumber(Math.min(source, dest));
		//in case that the appended new slide have the same name with the original slide
		hasCheckSlideName = false;
		checkAllSlideName();
		return OdfSlide.getInstance(cloneSlideElement);
	}

	/**
	 * Move the slide at a specified position to the destination position.
	 *
	 * @param source	the current index of the slide that need to be moved
	 * @param dest		The index of the destination position before the move action
	 * <p>
	 * Throw IndexOutOfBoundsException if the slide index is out of the presentation document slide count.
	 */
	public void moveSlide(int source, int dest) {
		checkAllSlideName();
		OfficePresentationElement contentRoot = null;
		try {
			contentRoot = getContentRoot();
		} catch (Exception e) {
			Logger.getLogger(OdfPresentationDocument.class.getName()).log(Level.SEVERE, null, e);
			return;
		}
		NodeList slideList = contentRoot.getElementsByTagNameNS(OdfDocumentNamespace.DRAW.getUri(), "page");
		int slideCount = slideList.getLength();
		if ((source < 0) || (source >= slideCount)
				|| (dest < 0) || (dest > slideCount)) {
			throw new IndexOutOfBoundsException("the specified Index is out of slide count when call moveSlide method.");
		}
		DrawPageElement sourceSlide = (DrawPageElement) slideList.item(source);
		if (dest == slideCount) {
			contentRoot.appendChild(sourceSlide);
		} else {
			DrawPageElement refSlide = (DrawPageElement) slideList.item(dest);
			contentRoot.insertBefore(sourceSlide, refSlide);
		}
		adjustNotePageNumber(Math.min(source, dest));
	}

	/**
	 * Append all the slides of the specified presentation document to the current document.
	 * @param srcDoc	the specified <code>OdfPresentationDocument</code> that need to be appended
	 */
	public void appendPresentation(OdfPresentationDocument srcDoc) {
		checkAllSlideName();
		OfficePresentationElement contentRoot = null;
		OdfFileDom contentDom = null;
		OfficePresentationElement srcContentRoot = null;
		try {
			contentRoot = getContentRoot();
			contentDom = getContentDom();
			srcContentRoot = srcDoc.getContentRoot();
		} catch (Exception e) {
			Logger.getLogger(OdfPresentationDocument.class.getName()).log(Level.SEVERE, null, e);
		}
		NodeList slideList = contentRoot.getElementsByTagNameNS(OdfDocumentNamespace.DRAW.getUri(), "page");
		int slideNum = slideList.getLength();
		//clone the srcContentRoot, and make a modification on this clone node.
		OfficePresentationElement srcCloneContentRoot = (OfficePresentationElement) srcContentRoot.cloneNode(true);
		//copy all the referred xlink:href here
		copyForeignLinkRef(srcCloneContentRoot);
		//copy all the referred style definition here
		copyForeignStyleRef(srcCloneContentRoot, srcDoc);
		Node child = srcCloneContentRoot.getFirstChild();
		while (child != null) {
			Node cloneElement = cloneForeignElement(child, contentDom, true);
			contentRoot.appendChild(cloneElement);
			child = child.getNextSibling();
		}
		adjustNotePageNumber(slideNum - 1);

		//in case that the appended new slide have the same name with the original slide
		hasCheckSlideName = false;
		checkAllSlideName();
	}

	/**
	 * Make a copy of slide which locates at the specified position of the source presentation document
	 * and insert it to the current presentation document at the new position.
	 * The original slide which at the dest index and after the dest index will move after.
	 * @param destIndex	the new position of the copied slide in the current document
	 * @param srcDoc	the source document of the copied slide
	 * @param srcIndex	the slide index of the source document that need to be copied
	 * @return the new slide which has the same content with the source slide
	 * <p>
	 * Throw IndexOutOfBoundsException if the slide index is out of the presentation document slide count
	 * If insert the foreign slide at the end of document, destIndex should set the same value
	 * with the slide count of the current presentation document.
	 */
	public OdfSlide copyForeignSlide(int destIndex,
			OdfPresentationDocument srcDoc, int srcIndex) {
		checkAllSlideName();
		OfficePresentationElement contentRoot = null;
		OdfFileDom contentDom = null;
		try {
			contentRoot = getContentRoot();
			contentDom = getContentDom();
		} catch (Exception e) {
			Logger.getLogger(OdfPresentationDocument.class.getName()).log(Level.SEVERE, null, e);
			return null;
		}
		NodeList slideList = contentRoot.getElementsByTagNameNS(OdfDocumentNamespace.DRAW.getUri(), "page");
		int slideCount = slideList.getLength();
		if ((destIndex < 0) || (destIndex > slideCount)) {
			throw new IndexOutOfBoundsException("the specified Index is out of slide count when call copyForeignSlide method.");
		}
		OdfSlide sourceSlide = srcDoc.getSlideByIndex(srcIndex);
		DrawPageElement sourceSlideElement = sourceSlide.getOdfElement();
		//clone the sourceSlideEle, and make a modification on this clone node.
		DrawPageElement sourceCloneSlideElement = (DrawPageElement) sourceSlideElement.cloneNode(true);

		//copy all the referred xlink:href here
		copyForeignLinkRef(sourceCloneSlideElement);
		//copy all the referred style definition here
		copyForeignStyleRef(sourceCloneSlideElement, srcDoc);
		//clone the sourceCloneSlideEle, and this cloned element should in the current dom tree
		DrawPageElement cloneSlideElement = (DrawPageElement) cloneForeignElement(sourceCloneSlideElement, contentDom, true);
		if (destIndex == slideCount) {
			contentRoot.appendChild(cloneSlideElement);
		} else {
			DrawPageElement refSlide = (DrawPageElement) slideList.item(destIndex);
			contentRoot.insertBefore(cloneSlideElement, refSlide);
		}
		adjustNotePageNumber(destIndex);
		//in case that the appended new slide have the same name with the original slide
		hasCheckSlideName = false;
		checkAllSlideName();
		return OdfSlide.getInstance(cloneSlideElement);
	}

	//clone the source clone element's referred object path to the current package
	//if the current package contains the same name with the referred object path,
	//rename the object path and path reference of this slide element
	//notes: the source clone element is the copied one to avoid changing the content of the source document.
	private void copyForeignLinkRef(OdfElement sourceCloneEle) {
		try {
			OdfFileDom fileDom = (OdfFileDom) sourceCloneEle.getOwnerDocument();
			XPath xpath;
			if (fileDom instanceof OdfContentDom) {
				xpath = ((OdfContentDom) fileDom).getXPath();
			} else {
				xpath = ((OdfStylesDom) fileDom).getXPath();
			}
			OdfPackageDocument srcDoc = fileDom.getDocument();
			//new a map to put the original name and the rename string, in case that the same name might be referred by the slide several times.
			HashMap<String, String> objectRenameMap = new HashMap<String, String>();
			NodeList linkNodes = (NodeList) xpath.evaluate(".//*[@xlink:href]", sourceCloneEle, XPathConstants.NODESET);
			for (int i = 0; i <= linkNodes.getLength(); i++) {
				OdfElement object = null;
				if (linkNodes.getLength() == i) {
					if (sourceCloneEle.hasAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "href")) {
						object = sourceCloneEle;
					} else {
						break;
					}
				} else {
					object = (OdfElement) linkNodes.item(i);
				}
				String refObjPath = object.getAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "href");
				if (refObjPath != null && refObjPath.length() > 0) {
					//the path of the object is start with "./"
					boolean hasPrefix = false;
					String prefix = "./";
					if (refObjPath.startsWith(prefix)) {
						refObjPath = refObjPath.substring(2);
						hasPrefix = true;
					}
					//check if the current document contains the same path
					OdfFileEntry fileEntry = getPackage().getFileEntry(refObjPath);
					//note: if refObjPath is a directory, it must end with '/'
					if (fileEntry == null) {
						fileEntry = getPackage().getFileEntry(refObjPath + "/");
					}
					String newObjPath = refObjPath;
					if (fileEntry != null) {
						//rename the object path
						newObjPath = objectRenameMap.get(refObjPath);
						if (newObjPath == null) {
							//if refObjPath still contains ".", it means that it has the suffix
							//then change the name before the suffix string
							int dotIndex = refObjPath.indexOf(".");
							if (dotIndex != -1) {
								newObjPath = refObjPath.substring(0, dotIndex) + "-" + makeUniqueName() + refObjPath.substring(dotIndex);
							} else {
								newObjPath = refObjPath + "-" + makeUniqueName();
							}
							objectRenameMap.put(refObjPath, newObjPath);
						}
						object.setAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "xlink:href", hasPrefix ? (prefix + newObjPath) : newObjPath);
					}
					InputStream is = srcDoc.getPackage().getInputStream(refObjPath);
					if (is != null) {
						String mediaType = srcDoc.getPackage().getFileEntry(refObjPath).getMediaTypeString();
						getPackage().insert(is, newObjPath, mediaType);
					} else {
						OdfDocument embedDoc = (OdfDocument) srcDoc.loadSubDocument(refObjPath);
						if (embedDoc != null) {
							insertDocument(embedDoc, newObjPath);
						}
					}
				}
			}
		} catch (Exception e) {
			Logger.getLogger(OdfPresentationDocument.class.getName()).log(Level.SEVERE, null, e);
		}
	}

	private void copyForeignStyleRef(OdfElement sourceCloneEle,
			OdfPresentationDocument doc) {
		try {
			OdfContentDom contentDom = getContentDom();
			XPath xpath = contentDom.getXPath();
			//1. collect all the referred style element which has "style:name" attribute
			//1.1. style:name of content.xml
			String styleQName = "style:name";
			NodeList srcStyleDefNodeList = (NodeList) xpath.evaluate("//*[@" + styleQName + "]", contentDom, XPathConstants.NODESET);
			HashMap<OdfElement, List<OdfElement>> srcContentStyleCloneEleList = new HashMap<OdfElement, List<OdfElement>>();
			HashMap<OdfElement, OdfElement> appendContentStyleList = new HashMap<OdfElement, OdfElement>();
			getCopyStyleList(null, sourceCloneEle, styleQName, srcStyleDefNodeList, srcContentStyleCloneEleList, appendContentStyleList, true);
			//1.2. style:name of styles.xml
			srcStyleDefNodeList = (NodeList) xpath.evaluate("//*[@" + styleQName + "]", doc.getStylesDom(), XPathConstants.NODESET);
			HashMap<OdfElement, List<OdfElement>> srcStylesStyleCloneEleList = new HashMap<OdfElement, List<OdfElement>>();
			HashMap<OdfElement, OdfElement> appendStylesStyleList = new HashMap<OdfElement, OdfElement>();
			getCopyStyleList(null, sourceCloneEle, styleQName, srcStyleDefNodeList, srcStylesStyleCloneEleList, appendStylesStyleList, true);
			//1.3 rename, copy the referred style element to the corresponding position in the dom tree
			insertCollectedStyle(styleQName, srcContentStyleCloneEleList, getContentDom(), appendContentStyleList);
			insertCollectedStyle(styleQName, srcStylesStyleCloneEleList, getStylesDom(), appendStylesStyleList);


			//2. collect all the referred style element which has "draw:name" attribute 
			//2.1 draw:name of styles.xml
			//the value of draw:name is string or StyleName,
			//only when the value is StyleName type, the style definition should be cloned to the destination document
			//in ODF spec, such attribute type is only exist in <office:styles> element, so only search it in styles.xml dom
			styleQName = "draw:name";
			srcStyleDefNodeList = (NodeList) xpath.evaluate("//*[@" + styleQName + "]", doc.getStylesDom(), XPathConstants.NODESET);
			HashMap<OdfElement, List<OdfElement>> srcDrawStyleCloneEleList = new HashMap<OdfElement, List<OdfElement>>();
			HashMap<OdfElement, OdfElement> appendDrawStyleList = new HashMap<OdfElement, OdfElement>();
			Iterator<OdfElement> iter = appendContentStyleList.keySet().iterator();
			while (iter.hasNext()) {
				OdfElement styleElement = iter.next();
				OdfElement cloneStyleElement = appendContentStyleList.get(styleElement);
				getCopyStyleList(styleElement, cloneStyleElement, styleQName, srcStyleDefNodeList, srcDrawStyleCloneEleList, appendDrawStyleList, false);
			}
			iter = appendStylesStyleList.keySet().iterator();
			while (iter.hasNext()) {
				OdfElement styleElement = iter.next();
				OdfElement cloneStyleElement = appendStylesStyleList.get(styleElement);
				getCopyStyleList(styleElement, cloneStyleElement, styleQName, srcStyleDefNodeList, srcDrawStyleCloneEleList, appendDrawStyleList, false);
			}
			//2.2 rename, copy the referred style element to the corresponding position in the dom tree
			//note: "draw:name" style element only exist in styles.dom
			insertCollectedStyle(styleQName, srcDrawStyleCloneEleList, getStylesDom(), appendDrawStyleList);

		} catch (Exception e) {
			Logger.getLogger(OdfPresentationDocument.class.getName()).log(Level.SEVERE, null, e);
		}

	}

	//1. modified the style name of the style definition element which has the same name with the source document
	//2. As to the style definition which match 1) condition, modified the referred style name of the element which reference this style
	//3. All the style which also contains other style reference, should be copied to the source document.
	private void insertCollectedStyle(String styleQName,
			HashMap<OdfElement, List<OdfElement>> srcStyleCloneEleList, OdfFileDom dom, HashMap<OdfElement, OdfElement> appendStyleList) {
		try {
			String stylePrefix = OdfNamespace.getPrefixPart(styleQName);
			String styleLocalName = OdfNamespace.getLocalPart(styleQName);
			String styleURI = OdfDocumentNamespace.STYLE.getUri();
			// is the DOM always the styles.xml
			XPath xpath = dom.getXPath();
			NodeList destStyleNodeList = (NodeList) xpath.evaluate("//*[@" + styleQName + "]", dom, XPathConstants.NODESET);

//			HashMap<String, String> styleRenameMap = new HashMap<String, String>();
			Iterator<OdfElement> iter = srcStyleCloneEleList.keySet().iterator();
			while (iter.hasNext()) {
				OdfElement styleElement = iter.next();
				OdfElement cloneStyleElement = appendStyleList.get(styleElement);
				if (cloneStyleElement == null) {
					cloneStyleElement = (OdfElement) styleElement.cloneNode(true);
					appendStyleList.put(styleElement, cloneStyleElement);
				}
				String styleName = styleElement.getAttributeNS(styleURI, styleLocalName);
				List<String> newStyleNameList = styleRenameMap.get(styleName);
				// if the newStyleNameList != null, means that styleName exists in dest document
				// and it has already been renamed
				if ((newStyleNameList != null)
						|| (isStyleNameExist(destStyleNodeList, styleName) != null)) {
					String newStyleName = null;
					if (newStyleNameList == null) {
						newStyleNameList = new ArrayList<String>();
						newStyleName = styleName + "-" + makeUniqueName();
						newStyleNameList.add(newStyleName);
						styleRenameMap.put(styleName, newStyleNameList);
					} else {
						for (int i = 0; i < newStyleNameList.size(); i++) {
							String styleNameIter = newStyleNameList.get(i);
							OdfElement destStyleElementWithNewName = isStyleNameExist(destStyleNodeList, styleNameIter);
							//check if the two style elements have the same content
							//if not, the cloneStyleElement should rename, rather than reuse the new style name
							cloneStyleElement.setAttributeNS(styleURI, styleQName, styleNameIter);
							if ((destStyleElementWithNewName != null) && destStyleElementWithNewName.equals(cloneStyleElement)) {
								newStyleName = styleNameIter;
								break;
							}
						}
						if (newStyleName == null) {
							newStyleName = styleName + "-" + makeUniqueName();
							newStyleNameList.add(newStyleName);
						}
					}
					// if newStyleName has been set in the element as the new name
					// which means that the newStyleName is conform to the odf spec
					// then change element style reference name
					if (changeStyleRefName(srcStyleCloneEleList.get(styleElement), styleName, newStyleName)) {
						cloneStyleElement.setAttributeNS(styleURI, styleQName, newStyleName);
						//if display name should also be renamed
						String displayName = cloneStyleElement.getAttributeNS(styleURI, "display-name");
						if ((displayName != null) && (displayName.length() > 0)) {
							cloneStyleElement.setAttributeNS(styleURI, stylePrefix + ":display-name",
									displayName + newStyleName.substring(newStyleName.length() - 8));
						}
					}

				}
			}

			iter = appendStyleList.keySet().iterator();
			while (iter.hasNext()) {
				OdfElement styleElement = iter.next();
				OdfElement cloneStyleElement = appendStyleList.get(styleElement);
				String newStyleName = cloneStyleElement.getAttributeNS(styleURI, styleLocalName);
				Boolean isAppended = styleAppendMap.get(newStyleName);
				//if styleAppendMap contain the newStyleName, 
				//means that cloneStyleElement has already been appended
				if ((isAppended != null) && isAppended.booleanValue() == true) {
					continue;
				} else {
					styleAppendMap.put(newStyleName, true);
				}
				OdfElement cloneForeignStyleElement = (OdfElement) cloneForeignElement(cloneStyleElement, dom, true);
				String styleElePath = getElementPath(styleElement);
				appendForeignStyleElement(cloneForeignStyleElement, dom, styleElePath);
				copyForeignLinkRef(cloneStyleElement);
			}
		} catch (Exception e) {
			Logger.getLogger(OdfPresentationDocument.class.getName()).log(Level.SEVERE, null, e);
		}

	}

	//get all the copy of referred style element which is directly referred or indirectly referred by cloneEle
	//all the style are defined in srcStyleNodeList
	//and these style are all have the styleName defined in styleQName attribute
	//the key of copyStyleEleList is the style definition element
	//the value of the corresponding key is the clone of the element which refer to the key, 
	//the cloned element can be the content of slide or the style element.
	//the key of appendStyleList is the style definition element which has the other style reference
	//the value of the corresponding key is the the style definition clone element
	//loop means if recursive call this function
	//if loop == true, get the style definition element reference other style definition element
	private void getCopyStyleList(OdfElement ele, OdfElement cloneEle, String styleQName, NodeList srcStyleNodeList,
			HashMap<OdfElement, List<OdfElement>> copyStyleEleList, HashMap<OdfElement, OdfElement> appendStyleList, boolean loop) {
		try {
			String styleLocalName = OdfNamespace.getLocalPart(styleQName);
			String styleURI = OdfDocumentNamespace.STYLE.getUri();
			//OdfElement override the "toString" method
			String cloneEleStr = cloneEle.toString();
			for (int i = 0; i < srcStyleNodeList.getLength(); i++) {
				OdfElement styleElement = (OdfElement) srcStyleNodeList.item(i);
				String styleName = styleElement.getAttributeNS(styleURI, styleLocalName);
				if (styleName != null) {
					int index = 0;
					index = cloneEleStr.indexOf("=\"" + styleName + "\"", index);
					while (index >= 0) {
						String subStr = cloneEleStr.substring(0, index);
						int lastSpaceIndex = subStr.lastIndexOf(' ');
						String attrStr = subStr.substring(lastSpaceIndex + 1, index);
						XPath xpath = ((OdfFileDom) cloneEle.getOwnerDocument()).getXPath();
						NodeList styleRefNodes = (NodeList) xpath.evaluate(".//*[@" + attrStr + "='" + styleName + "']", cloneEle, XPathConstants.NODESET);
						boolean isExist = false;
						for (int j = 0; j <= styleRefNodes.getLength(); j++) {
							OdfElement styleRefElement = null;
							if (j == styleRefNodes.getLength()) {
								isExist = isStyleNameRefExist(cloneEle, styleName, false);
								if (isExist) {
									styleRefElement = cloneEle;
								} else {
									continue;
								}
							} else {
								OdfElement tmpElement = (OdfElement) styleRefNodes.item(j);
								if (isStyleNameRefExist(tmpElement, styleName, false)) {
									styleRefElement = tmpElement;
								} else {
									continue;
								}
							}
							boolean hasLoopStyleDef = true;
							if (copyStyleEleList.get(styleElement) == null) {
								List<OdfElement> styleRefEleList = new ArrayList<OdfElement>();
								copyStyleEleList.put(styleElement, styleRefEleList);
								hasLoopStyleDef = false;
							}
							copyStyleEleList.get(styleElement).add(styleRefElement);

							OdfElement cloneStyleElement = appendStyleList.get(styleElement);
							if (cloneStyleElement == null) {
								cloneStyleElement = (OdfElement) styleElement.cloneNode(true);
								appendStyleList.put(styleElement, cloneStyleElement);
							}
							if (loop && !hasLoopStyleDef) {
								getCopyStyleList(styleElement, cloneStyleElement, styleQName, srcStyleNodeList, copyStyleEleList, appendStyleList, loop);
							}
						}
						index = cloneEleStr.indexOf("=\"" + styleName + "\"", index + styleName.length());
					}
				}
			}
		} catch (Exception e) {
			Logger.getLogger(OdfPresentationDocument.class.getName()).log(Level.SEVERE, null, e);
		}
	}
	//append the cloneStyleElement to the contentDom which position is defined by styleElePath

	private void appendForeignStyleElement(OdfElement cloneStyleEle,
			OdfFileDom dom, String styleElePath) {
		StringTokenizer token = new StringTokenizer(styleElePath, "/");
		boolean isExist = true;
		Node iterNode = dom.getFirstChild();
		Node parentNode = dom;
		while (token.hasMoreTokens()) {
			String onePath = token.nextToken();

			while ((iterNode != null) && isExist) {
				String path = iterNode.getNamespaceURI();
				String prefix = iterNode.getPrefix();
				if (prefix == null) {
					path += "@" + iterNode.getLocalName();
				} else {
					path += "@" + prefix + ":" + iterNode.getLocalName();
				}
				if (!path.equals(onePath)) {
					//not found, then get the next sibling to find such path node
					iterNode = iterNode.getNextSibling();
				} else {
					//found, then get the child nodes to find the next path node
					parentNode = iterNode;
					iterNode = iterNode.getFirstChild();
					break;
				}
			}

			if (iterNode == null) {
				//should new the element since the current path node
				if (isExist) {
					isExist = false;
				}
				StringTokenizer token2 = new StringTokenizer(onePath, "@");
				OdfElement newElement = dom.createElementNS(OdfName.newName(token2.nextToken(), token2.nextToken()));
				parentNode.appendChild(newElement);
				parentNode = newElement;
			}
		}
		parentNode.appendChild(cloneStyleEle);
	}

	//The returned string is a path from the top of the dom tree to the specified element
	//and the path is split by "/" between each node
	private String getElementPath(OdfElement styleEle) {
		String path = "";
		Node parentNode = styleEle.getParentNode();
		while (!(parentNode instanceof OdfFileDom)) {
			String qname = null;
			String prefix = parentNode.getPrefix();
			if (prefix == null) {
				qname = parentNode.getLocalName();
			} else {
				qname = prefix + ":" + parentNode.getLocalName();
			}
			path = parentNode.getNamespaceURI() + "@" + qname + "/" + path;
			parentNode = parentNode.getParentNode();
		}
		return path;
	}

	//change the element referred oldStyleName to the new name
	//if true then set newStyleName attribute value successfully
	//if false means that the newStyleName value is not conform to the ODF spec, so do not modify the oldStyleName
	private boolean changeStyleRefName(List<OdfElement> list, String oldStyleName, String newStyleName) {
		boolean rtn = false;
		for (int index = 0; index < list.size(); index++) {
			OdfElement element = list.get(index);
			NamedNodeMap attributes = element.getAttributes();

			if (attributes != null) {
				for (int i = 0; i < attributes.getLength(); i++) {
					Node item = attributes.item(i);
					String value = item.getNodeValue();
					if (oldStyleName.equals(value)) {
						try {
							item.setNodeValue(newStyleName);
							rtn = true;
							break;
						} catch (IllegalArgumentException e) {
							return false;
						}
					}
				}
			}
		}
		return rtn;
	}

	//check if the element contains the referred styleName
	private boolean isStyleNameRefExist(Node element, String styleName, boolean deep) {
		NamedNodeMap attributes = element.getAttributes();
		if (attributes != null) {
			for (int i = 0; i < attributes.getLength(); i++) {
				Node item = attributes.item(i);
				if (item.getNodeValue().equals(styleName)
						&& !item.getNodeName().equals("style:name")) //this is style definition, not reference
				{
					return true;
				}
			}
		}
		if (deep) {
			Node childNode = element.getFirstChild();
			while (childNode != null) {
				if (!isStyleNameRefExist(childNode, styleName, true)) {
					childNode = childNode.getNextSibling();
				} else {
					return true;
				}
			}
		}
		return false;
	}

	//check if nodeList contains the node that "style:name" attribute has the same value with styleName
	//Note: nodeList here is all the style definition list
	private OdfElement isStyleNameExist(NodeList nodeList,
			String styleName) {
		for (int i = 0; i < nodeList.getLength(); i++) {
			OdfElement element = (OdfElement) nodeList.item(i);
			String name = element.getAttributeNS(OdfDocumentNamespace.STYLE.getUri(), "name");
			if (name.equals(styleName)) //return true;
			{
				return element;
			}
		}
		//return false;
		return null;
	}

	private String makeUniqueName() {
		return String.format("a%06x", (int) (Math.random() * 0xffffff));
	}

	/**
	 * Make a content copy of the specified element, 
	 * and the returned element should have the specified ownerDocument.
	 * @param element	The element that need to be copied
	 * @param dom		The specified DOM tree that the returned element belong to
	 * @param deep		If true, recursively clone the subtree under the element,
	 * 					false, only clone the element itself
	 * @return	Returns a duplicated element which is not in the DOM tree with the specified element
	 */
	public Node cloneForeignElement(Node element, OdfFileDom dom, boolean deep) {
		checkAllSlideName();
		if (element instanceof OdfElement) {
			OdfElement cloneElement = dom.createElementNS(((OdfElement) element).getOdfName());

			NamedNodeMap attributes = element.getAttributes();
			if (attributes != null) {
				for (int i = 0; i < attributes.getLength(); i++) {
					Node item = attributes.item(i);
					String qname = null;
					String prefix = item.getPrefix();
					if (prefix == null) {
						qname = item.getLocalName();
					} else {
						qname = prefix + ":" + item.getLocalName();
					}

					cloneElement.setAttributeNS(item.getNamespaceURI(), qname, item.getNodeValue());
				}
			}

			if (deep) {
				Node childNode = element.getFirstChild();
				while (childNode != null) {
					cloneElement.appendChild(cloneForeignElement(childNode, dom, true));
					childNode = childNode.getNextSibling();
				}
			}

			return cloneElement;
		} else {
			return dom.createTextNode(element.getNodeValue());
		}

	}

	/**
	 * New a slide at the specified position with the specified name,
	 * and use the specified slide template.
	 * See <code>OdfDrawPage.SlideLayout</code>.
	 * <p>
	 * If index is invalid, such as larger than the current document 
	 * slide number or is negative,
	 * then append the new slide at the end of the document.
	 * <p>
	 * The slide name can be null.
	 * @param index		the new slide position
	 * @param name		the new slide name
	 * @param slideLayout	the new slide template
	 * @return	the new slide which locate at the specified position 
	 * with the specified name and apply the specified slide template.
	 * If slideLayout is null, then use the default slide template which is a blank slide.
	 * <p>
	 * Throw IndexOutOfBoundsException if index is out of the presentation document slide count.
	 */
	public OdfSlide newSlide(int index, String name, OdfSlide.SlideLayout slideLayout) {
		checkAllSlideName();
		OfficePresentationElement contentRoot = null;
		try {
			contentRoot = getContentRoot();
		} catch (Exception e) {
			Logger.getLogger(OdfPresentationDocument.class.getName()).log(Level.SEVERE, null, e);
			return null;
		}
		NodeList slideList = contentRoot.getElementsByTagNameNS(OdfDocumentNamespace.DRAW.getUri(), "page");
		int slideCount = slideList.getLength();
		if ((index < 0) || (index > slideCount)) {
			throw new IndexOutOfBoundsException("the specified Index is out of slide count when call newSlide method.");
		}
		//if insert page at the beginning of the document,
		//get the next page style as the new page style
		//else get the previous page style as the new page style
		DrawPageElement refStyleSlide = null;
		int refSlideIndex = 0;
		if (index > 0) {
			refSlideIndex = index - 1;
		}
		refStyleSlide = (DrawPageElement) slideList.item(refSlideIndex);
		String masterPageStyleName = "Default";
		String masterName = refStyleSlide.getDrawMasterPageNameAttribute();
		if (masterName != null) {
			masterPageStyleName = masterName;
		}
		DrawPageElement newSlideElement = contentRoot.newDrawPageElement(masterPageStyleName);
		newSlideElement.setDrawNameAttribute(name);
		String drawStyleName = refStyleSlide.getDrawStyleNameAttribute();
		if (drawStyleName != null) {
			newSlideElement.setDrawStyleNameAttribute(drawStyleName);
		}
		String pageLayoutName = refStyleSlide.getPresentationPresentationPageLayoutNameAttribute();
		if (pageLayoutName != null) {
			newSlideElement.setPresentationPresentationPageLayoutNameAttribute(pageLayoutName);
		}
		setSlideLayout(newSlideElement, slideLayout);
		//insert notes page
		NodeList noteNodes = refStyleSlide.getElementsByTagNameNS(OdfDocumentNamespace.PRESENTATION.getUri(), "notes");
		if (noteNodes.getLength() > 0) {
			PresentationNotesElement notePage = (PresentationNotesElement) noteNodes.item(0);
			PresentationNotesElement cloneNotePage = (PresentationNotesElement) notePage.cloneNode(true);
			newSlideElement.appendChild(cloneNotePage);
		}
		if (index < slideCount) {
			DrawPageElement refSlide = (DrawPageElement) slideList.item(index);
			contentRoot.insertBefore(newSlideElement, refSlide);
		}
		adjustNotePageNumber(index);
		//in case that the appended new slide have the same name with the original slide
		hasCheckSlideName = false;
		checkAllSlideName();
		return OdfSlide.getInstance(newSlideElement);
	}

	//when insert a slide, the note page for this slide is also inserted.
	//note page refer the slide index in order to show the corresponding slide notes view
	//this function is used to adjust note page referred slide index since startIndex
	//when the slide at startIndex has been delete or insert
	private void adjustNotePageNumber(int startIndex) {
		try {
			OfficePresentationElement contentRoot = getContentRoot();
			NodeList slideList = contentRoot.getElementsByTagNameNS(OdfDocumentNamespace.DRAW.getUri(), "page");
			for (int i = startIndex; i < getSlideCount(); i++) {
				DrawPageElement page = (DrawPageElement) slideList.item(i);
				NodeList noteNodes = page.getElementsByTagNameNS(OdfDocumentNamespace.PRESENTATION.getUri(), "notes");
				if (noteNodes.getLength() > 0) {
					PresentationNotesElement notePage = (PresentationNotesElement) noteNodes.item(0);
					NodeList thumbnailList = notePage.getElementsByTagNameNS(OdfDocumentNamespace.DRAW.getUri(), "page-thumbnail");
					if (thumbnailList.getLength() > 0) {
						DrawPageThumbnailElement thumbnail = (DrawPageThumbnailElement) thumbnailList.item(0);
						thumbnail.setDrawPageNumberAttribute(i + 1);
					}
				}
			}
		} catch (Exception e) {
			Logger.getLogger(OdfPresentationDocument.class.getName()).log(Level.SEVERE, null, e);
		}
	}

	//covered element
	//<presentation:notes>, <draw:page-thumbnail>, <draw:frame>
	//<style:presentation-page-layout>
	private void setSlideLayout(DrawPageElement page,
			OdfSlide.SlideLayout slideLayout) {
		if (slideLayout == null) {
			slideLayout = OdfSlide.SlideLayout.BLANK;
		}
		OdfOfficeStyles styles = getOrCreateDocumentStyles();
		String layoutName;

		if (slideLayout.toString().equals(OdfSlide.SlideLayout.TITLE_ONLY.toString())) {
			layoutName = "AL1T" + makeUniqueName();
			try {
				StylePresentationPageLayoutElement layout = styles.newStylePresentationPageLayoutElement(layoutName);
				layout.newPresentationPlaceholderElement("title", "2.058cm", "1.743cm", "23.91cm", "3.507cm");
			} catch (Exception e1) {
				Logger.getLogger(OdfPresentationDocument.class.getName()).log(Level.SEVERE, null, e1);
			}
			page.setPresentationPresentationPageLayoutNameAttribute(layoutName);

			DrawFrameElement frame1 = page.newDrawFrameElement();
			frame1.setProperty(StyleGraphicPropertiesElement.StyleShadow, "true");
			frame1.setProperty(StyleGraphicPropertiesElement.AutoGrowHeight, "true");
			frame1.setProperty(StyleGraphicPropertiesElement.MinHeight, "3.507");
			frame1.setPresentationStyleNameAttribute(frame1.getStyleName());

			frame1.setDrawLayerAttribute("layout");
			frame1.setSvgHeightAttribute("3.006cm");
			frame1.setSvgWidthAttribute("24.299cm");
			frame1.setSvgXAttribute("1.35cm");
			frame1.setSvgYAttribute("0.717cm");
			frame1.setPresentationClassAttribute(PresentationClassAttribute.Value.TITLE.toString());
			frame1.setPresentationPlaceholderAttribute(true);
			frame1.newDrawTextBoxElement();
		} else if (slideLayout.toString().equals(OdfSlide.SlideLayout.TITLE_OUTLINE.toString())) {
			layoutName = makeUniqueName();
			try {
				styles = super.getStylesDom().getOfficeStyles();
				if (styles == null) {
					styles = super.getStylesDom().newOdfElement(OdfOfficeStyles.class);
				}
				StylePresentationPageLayoutElement layout = styles.newStylePresentationPageLayoutElement(layoutName);
				layout.newPresentationPlaceholderElement("title", "2.058cm", "1.743cm", "23.91cm", "3.507cm");
				layout.newPresentationPlaceholderElement("outline", "2.058cm", "1.743cm", "23.91cm", "3.507cm");

			} catch (Exception e1) {
				Logger.getLogger(OdfPresentationDocument.class.getName()).log(Level.SEVERE, null, e1);
			}
			page.setPresentationPresentationPageLayoutNameAttribute(layoutName);


			DrawFrameElement frame1 = page.newDrawFrameElement();
			frame1.setProperty(StyleGraphicPropertiesElement.StyleShadow, "true");
			frame1.setProperty(StyleGraphicPropertiesElement.AutoGrowHeight, "true");
			frame1.setProperty(StyleGraphicPropertiesElement.MinHeight, "3.507");
			frame1.setPresentationStyleNameAttribute(frame1.getStyleName());

			frame1.setDrawLayerAttribute("layout");
			frame1.setSvgHeightAttribute("3.006cm");
			frame1.setSvgWidthAttribute("24.299cm");
			frame1.setSvgXAttribute("1.35cm");
			frame1.setSvgYAttribute("0.717cm");
			frame1.setPresentationClassAttribute(PresentationClassAttribute.Value.TITLE.toString());
			frame1.setPresentationPlaceholderAttribute(true);
			frame1.newDrawTextBoxElement();
			DrawFrameElement frame2 = page.newDrawFrameElement();

			frame2.setProperty(StyleGraphicPropertiesElement.FillColor, "#ffffff");
			frame2.setProperty(StyleGraphicPropertiesElement.MinHeight, "13.114");
			frame2.setPresentationStyleNameAttribute(frame2.getStyleName());

			frame2.setDrawLayerAttribute("layout");
			frame2.setSvgHeightAttribute("11.629cm");
			frame2.setSvgWidthAttribute("24.199cm");
			frame2.setSvgXAttribute("1.35cm");
			frame2.setSvgYAttribute("4.337cm");
			frame2.setPresentationClassAttribute(PresentationClassAttribute.Value.OUTLINE.toString());
			frame2.setPresentationPlaceholderAttribute(true);
			frame2.newDrawTextBoxElement();
		} else if (slideLayout.toString().equals(OdfSlide.SlideLayout.TITLE_PLUS_TEXT.toString())) {
			layoutName = makeUniqueName();
			try {
				styles = super.getStylesDom().getOfficeStyles();
				if (styles == null) {
					styles = super.getStylesDom().newOdfElement(OdfOfficeStyles.class);
				}
				StylePresentationPageLayoutElement layout = styles.newStylePresentationPageLayoutElement(layoutName);
				layout.newPresentationPlaceholderElement("title", "2.058cm", "1.743cm", "23.91cm", "1.743cm");
				layout.newPresentationPlaceholderElement("subtitle", "2.058cm", "5.838cm", "23.91cm", "13.23cm");

			} catch (Exception e1) {
				Logger.getLogger(OdfPresentationDocument.class.getName()).log(Level.SEVERE, null, e1);
			}
			page.setPresentationPresentationPageLayoutNameAttribute(layoutName);

			DrawFrameElement frame1 = page.newDrawFrameElement();
			frame1.setProperty(StyleGraphicPropertiesElement.AutoGrowHeight, "true");
			frame1.setProperty(StyleGraphicPropertiesElement.MinHeight, "3.507");
			frame1.setPresentationStyleNameAttribute(frame1.getStyleName());

			frame1.setDrawLayerAttribute("layout");
			frame1.setSvgHeightAttribute("3.006cm");
			frame1.setSvgWidthAttribute("24.299cm");
			frame1.setSvgXAttribute("1.35cm");
			frame1.setSvgYAttribute("0.717cm");
			frame1.setPresentationClassAttribute(PresentationClassAttribute.Value.TITLE.toString());
			frame1.setPresentationPlaceholderAttribute(true);
			frame1.newDrawTextBoxElement();
			DrawFrameElement frame2 = page.newDrawFrameElement();
			frame2.setProperty(StyleGraphicPropertiesElement.AutoGrowHeight, "true");
			frame2.setProperty(StyleGraphicPropertiesElement.MinHeight, "3.507");
			frame2.setPresentationStyleNameAttribute(frame2.getStyleName());

			frame2.setDrawLayerAttribute("layout");
			frame2.setSvgHeightAttribute("11.88cm");
			frame2.setSvgWidthAttribute("24.299cm");
			frame2.setSvgXAttribute("1.35cm");
			frame2.setSvgYAttribute("4.712cm");
			frame2.setPresentationClassAttribute(PresentationClassAttribute.Value.SUBTITLE.toString());
			frame2.setPresentationPlaceholderAttribute(true);
			frame2.newDrawTextBoxElement();

		} else if (slideLayout.toString().equals(OdfSlide.SlideLayout.TITLE_PLUS_2_TEXT_BLOCK.toString())) {

			layoutName = makeUniqueName();
			try {
				styles = super.getStylesDom().getOfficeStyles();
				if (styles == null) {
					styles = super.getStylesDom().newOdfElement(OdfOfficeStyles.class);
				}
				StylePresentationPageLayoutElement layout = styles.newStylePresentationPageLayoutElement(layoutName);
				layout.newPresentationPlaceholderElement("outline", "2.058cm", "1.743cm", "23.91cm", "1.743cm");
				layout.newPresentationPlaceholderElement("outline", "1.35cm", "4.212cm", "11.857cm", "11.629cm");
				layout.newPresentationPlaceholderElement("outline", "4.212cm", "13.8cm", "11.857cm", "11.629cm");

			} catch (Exception e1) {
				Logger.getLogger(OdfPresentationDocument.class.getName()).log(Level.SEVERE, null, e1);
			}

			DrawFrameElement frame1 = page.newDrawFrameElement();
			frame1.setProperty(StyleGraphicPropertiesElement.AutoGrowHeight, "true");
			frame1.setProperty(StyleGraphicPropertiesElement.MinHeight, "3.507");
			frame1.setPresentationStyleNameAttribute(frame1.getStyleName());

			frame1.setDrawLayerAttribute("layout");
			frame1.setSvgHeightAttribute("3.006cm");
			frame1.setSvgWidthAttribute("24.299cm");
			frame1.setSvgXAttribute("1.35cm");
			frame1.setSvgYAttribute("0.717cm");
			frame1.setPresentationClassAttribute(PresentationClassAttribute.Value.TITLE.toString());
			frame1.setPresentationPlaceholderAttribute(true);
			frame1.newDrawTextBoxElement();
			DrawFrameElement frame2 = page.newDrawFrameElement();
			frame2.setProperty(StyleGraphicPropertiesElement.AutoGrowHeight, "true");
			frame2.setProperty(StyleGraphicPropertiesElement.MinHeight, "3.507");
			frame2.setPresentationStyleNameAttribute(frame2.getStyleName());

			frame2.setDrawLayerAttribute("layout");
			frame2.setSvgHeightAttribute("11.629cm");
			frame2.setSvgWidthAttribute("11.857cm");
			frame2.setSvgXAttribute("1.35cm");
			frame2.setSvgYAttribute("4.212cm");
			frame2.setPresentationClassAttribute(PresentationClassAttribute.Value.OUTLINE.toString());
			frame2.setPresentationPlaceholderAttribute(true);
			frame2.newDrawTextBoxElement();
			DrawFrameElement frame3 = page.newDrawFrameElement();
			frame3.setProperty(StyleGraphicPropertiesElement.AutoGrowHeight, "true");
			frame3.setProperty(StyleGraphicPropertiesElement.MinHeight, "3.507");
			frame3.setPresentationStyleNameAttribute(frame3.getStyleName());

			frame3.setDrawLayerAttribute("layout");
			frame3.setSvgHeightAttribute("11.62cm");
			frame3.setSvgWidthAttribute("11.857cm");
			frame3.setSvgXAttribute("13.8cm");
			frame3.setSvgYAttribute("4.212cm");
			frame3.setPresentationClassAttribute(PresentationClassAttribute.Value.OUTLINE.toString());
			frame3.setPresentationPlaceholderAttribute(true);
			frame3.newDrawTextBoxElement();

			page.setPresentationPresentationPageLayoutNameAttribute(layoutName);

		}
	}
}
